import { ExternalLink } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { t, tOpt } from '@/i18n'; import { isNotBuilt, isNotFound } from '@/lib/api'; import { apiExplore } from '@/lib/api-explore'; import { cn } from '@/lib/cn'; import { compact, formatDate, grouped } from '@/lib/format'; import { routes } from '@/lib/site'; import type { ImportRun, SourceIndicatorRow, SourceResponse } from '@/lib/types-explore'; import { DataTable, type DataTableColumn } from '@/components/data/data-table'; import { NotBuiltState } from '@/components/data/empty-state'; import { FreshnessBadge } from '@/components/data/freshness-badge'; import { Section } from '@/components/data/section'; import { ACTION_CLS, PageHeader } from '@/components/explore/page-header'; import { FooterCredits } from '@/components/layout/site-footer'; export const revalidate = 900; type Params = { id: string }; async function load(id: string): Promise { try { return await apiExplore.source(id, 60); } catch (e) { if (isNotFound(e)) return null; if (isNotBuilt(e)) return 'not-built'; throw e; } } export async function generateMetadata({ params }: { params: Promise }): Promise { const { id } = await params; const data = await load(id); if (!data || data === 'not-built') return { title: t('source.notFound'), robots: { index: false } }; const s = data.source; const title = t('source.title', { name: s.name ?? s.id }); const description = t('source.description', { name: s.name ?? s.id, n: s.n_indicators ?? data.indicators.length, licence: s.licence ?? '' }); return { title, description, alternates: { canonical: routes.source(s.id) }, openGraph: { title: `${title} — ${t('site.name')}`, description, url: routes.source(s.id), type: 'article' } }; } function RunStatus({ status }: { status: string | null }) { const s = (status ?? '').toLowerCase(); const label = tOpt(`source.run.${s}`, status ?? t('common.na')); return {label}; } function duration(r: ImportRun): string { if (!r.started_at || !r.finished_at) return t('common.na'); const s = (new Date(r.finished_at).getTime() - new Date(r.started_at).getTime()) / 1000; if (!Number.isFinite(s) || s < 0) return t('common.na'); return s < 60 ? `${Math.round(s)} s` : `${Math.round(s / 60)} min`; } export default async function SourcePage({ params }: { params: Promise }) { const { id } = await params; const data = await load(id); if (data === null) notFound(); if (data === 'not-built') return ; const s = data.source; const name = s.name ?? s.id; const indCols: DataTableColumn[] = [ { key: 'indicator', header: t('source.series.indicator'), cell: (r) => ( {r.name ?? r.slug} ), }, { key: 'code', header: t('source.series.code'), cell: (r) => {[r.dataset, r.series_code].filter(Boolean).join(' · ')} }, { key: 'priority', header: t('source.series.priority'), numeric: true, cell: (r) => (r.priority != null ? `#${r.priority}` : t('common.na')) }, { key: 'coverage', header: t('source.series.coverage'), numeric: true, cell: (r) => (r.n_countries != null ? `${grouped(r.n_countries)} · ${r.n_observations != null ? compact(r.n_observations) : ''}` : t('common.na')) }, { key: 'last', header: t('source.series.lastYear'), numeric: true, cell: (r) => r.last_year ?? t('common.na'), hideOnMobile: true }, { key: 'status', header: t('source.series.status'), cell: (r) => }, ]; const runCols: DataTableColumn[] = [ { key: 'started', header: t('source.runs.started'), cell: (r) => {r.started_at ? `${formatDate(r.started_at)} ${r.started_at.slice(11, 16)}` : t('common.na')} }, { key: 'dataset', header: t('source.runs.dataset'), cell: (r) => {r.dataset} }, { key: 'status', header: t('source.runs.status'), cell: (r) => }, { key: 'rows', header: t('source.runs.rows'), numeric: true, cell: (r) => (r.rows_valid != null ? grouped(r.rows_valid) : t('common.na')) }, { key: 'warn', header: t('source.runs.warnings'), numeric: true, cell: (r) => (r.warnings != null ? grouped(r.warnings) : t('common.na')), hideOnMobile: true }, { key: 'err', header: t('source.runs.errors'), numeric: true, cell: (r) => (r.errors ? {grouped(r.errors)} : r.errors === 0 ? '0' : t('common.na')), hideOnMobile: true }, { key: 'dur', header: t('source.runs.duration'), numeric: true, cell: (r) => duration(r), hideOnMobile: true }, ]; return ( <> {s.id} {s.licence ? ` · ${s.licence}` : ''} {s.n_indicators != null ? ` · ${t('sources.indicators', { n: grouped(s.n_indicators) })}` : ''} {s.n_observations != null ? ` · ${t('sources.observations', { n: compact(s.n_observations) })}` : ''} } actions={ s.url ? ( {t('source.open', { name })} ) : null } />
{t('source.freshness.sourceUpdated')}
{formatDate(data.freshness.source_updated_at)}
{t('source.freshness.retrieved')}
{formatDate(s.last_success_at ?? data.freshness.retrieved_at)}
{t('source.freshness.built')}
{formatDate(data.freshness.built_at)}
{t('sources.organisation')}
{s.organization ?? t('common.na')}
{t('sources.licence')}
{s.licence ?? t('common.na')}
{t('sources.attribution')}
{s.attribution ?? t('common.na')}
{t('sources.apiBase')}
{s.api_base ?? t('common.na')}
{s.notes ? (
{s.notes}
) : null}
{data.datasets.length ? (
d.dataset ?? 'null'} caption={t('source.datasets.title')} columns={[ { key: 'dataset', header: t('source.datasets.dataset'), cell: (d) => {d.dataset ?? t('common.na')} }, { key: 'obs', header: t('source.datasets.observations'), numeric: true, cell: (d) => (d.n_observations != null ? grouped(d.n_observations) : t('common.na')) }, { key: 'ind', header: t('source.datasets.indicators'), numeric: true, cell: (d) => (d.n_indicators != null ? grouped(d.n_indicators) : t('common.na')) }, { key: 'cty', header: t('source.datasets.countries'), numeric: true, cell: (d) => (d.n_countries != null ? grouped(d.n_countries) : t('common.na')) }, { key: 'years', header: t('source.datasets.years'), numeric: true, cell: (d) => (d.first_year && d.last_year ? `${d.first_year}–${d.last_year}` : t('common.na')) }, ]} />
) : null}
{data.indicators.length ? `${r.slug}-${r.series_code}`} columns={indCols} caption={t('source.series.title')} dense /> :

{t('common.noDataLong')}

}
{data.import_runs.length ? `${r.run_id}-${r.dataset}`} columns={runCols} caption={t('source.runs.title')} dense /> :

{t('source.runs.none')}

}
{t('sources.contact')}
); }